require "tuning"
local PLANT_DEFS = require("prefabs/farm_plant_defs").PLANT_DEFS

local function MakeVegStats(seedweight, hunger, health, perish_time, sanity,
                            cooked_hunger, cooked_health, cooked_perish_time, cooked_sanity,
                            float_settings, cooked_float_settings, dryable, secondary_foodtype,
                            halloweenmoonmutable_settings, lure_data, issnowmandecor)
    return {
        seed_weight = seedweight,                                      -- 种子权重
        hunger = hunger,                                               -- 饥饿值
        health = health,                                               -- 生命值
        perishtime = perish_time,                                      -- 腐烂时间
        sanity = sanity,                                               -- 精神值
        cooked_hunger = cooked_hunger,                                 -- 烤熟后的饥饿值
        cooked_health = cooked_health,                                 -- 烤熟后的生命值
        cooked_perishtime = cooked_perish_time,                        -- 烤熟后的腐烂时间
        cooked_sanity = cooked_sanity,                                 -- 烤熟后的精神值
        float_settings = float_settings,                               -- 水中漂浮参数
        cooked_float_settings = cooked_float_settings,                 -- 烤熟后的水中漂浮参数
        dryable = dryable,                                             -- 可否晾晒
        secondary_foodtype = secondary_foodtype,                       -- 额外食物类型
        halloweenmoonmutable_settings = halloweenmoonmutable_settings, -- 月亮精华液变异函数(原版火龙果,胡萝卜)
        lure_data = lure_data,                                         -- 鱼饵数据
        issnowmandecor = issnowmandecor,                               -- 雪人装饰
    }
end

local COMMON = TUNING.SEED_CHANCE_COMMON     -- 种子权重 2.5
local UNCOMMON = TUNING.SEED_CHANCE_UNCOMMON -- 种子权重 1
local RARE = TUNING.SEED_CHANCE_RARE         -- 种子权重 0.5

local OVERSIZED_PHYSICS_RADIUS = 0.1         -- 巨大作物物理半径
local OVERSIZED_MAXWORK = 1                  -- 巨大作物最大工作次数
local OVERSIZED_PERISHTIME_MULT = 4          -- 巨大作物腐烂时间是普通作物的多少倍

-- 官方是全局表,我这里就用局部表
local SW_VEGGIES =
{
    sw_piggymimosa = MakeVegStats(nil, 3, 3, TUNING.PERISH_SLOW, 5,
        9, 5, TUNING.PERISH_FASTISH, 8,
        { "small", 0.05, 0.9 }, { "small", 0.05, 0.8 }),
}

-- 遍历 SW_VEGGIES 表，将其内容添加到 VEGGIES
-- for key, value in pairs(SW_VEGGIES) do
--     VEGGIES[key] = value
-- end

local SEEDLESS =
{
    -- berries = true,
}

local assets_seeds =
{
    Asset("ANIM", "anim/seeds.zip"),
    Asset("ANIM", "anim/sw_farm_plant_seeds.zip"),
}

-- 可以农场土堆栽种
local function can_plant_seed(inst, pt, mouseover, deployer)
    local x, z = pt.x, pt.z
    return TheWorld.Map:CanTillSoilAtPoint(x, 0, z, true)
end

-- 植物人专属部署函数
local function OnDeploy(inst, pt, deployer)
    local plant = SpawnPrefab(inst.components.farmplantable.plant)
    plant.Transform:SetPosition(pt.x, 0, pt.z)
    plant:PushEvent("on_planted", { in_soil = false, doer = deployer, seed = inst })
    TheWorld.Map:CollapseSoilAtPoint(pt.x, 0, pt.z)
    inst:Remove()
end

-- 巨大作物重量计算函数,返回0-1的比例
local function oversized_calcweightcoefficient(name)
    if PLANT_DEFS[name].weight_data[3] ~= nil and math.random() < PLANT_DEFS[name].weight_data[3] then
        return (math.random() + math.random()) / 2
    else
        return math.random()
    end
end

-- 巨大作物装备函数
local function oversized_onequip(inst, owner)
    local swap = inst.components.symbolswapdata
    owner.AnimState:OverrideSymbol("swap_body", swap.build, swap.symbol)
end

-- 巨大作物卸下函数
local function oversized_onunequip(inst, owner)
    owner.AnimState:ClearOverrideSymbol("swap_body")
end

--敲击完巨大作物的回调函数
local function oversized_onfinishwork(inst, chopper)
    inst.components.lootdropper:DropLoot()
    inst:Remove()
end

-- 巨大作物燃烧后的回调函数
local function oversized_onburnt(inst)
    inst.components.lootdropper:DropLoot()
    inst:Remove()
end

-- 设置巨大作物的掉落物
local function oversized_makeloots(inst, name)
    local product = name
    local seeds = name .. "_seeds"
    return { product, product, seeds, seeds, math.random() < 0.75 and product or seeds }
end

-- 巨大作物健身房上的腐烂时间
local function oversized_onperish(inst)
    -- 放到健身房上
    local owner = inst.components.inventoryitem:GetGrandOwner()
    local gym = owner and owner:HasTag("gym") and owner or nil
    local rot = nil
    local slot = nil

    if owner and gym == nil then
        local loots = {}
        for i = 1, #inst.components.lootdropper.loot do
            table.insert(loots, "spoiled_food")
        end
        inst.components.lootdropper:SetLoot(loots)
        inst.components.lootdropper:DropLoot()
    else
        rot = SpawnPrefab(inst.prefab .. "_rotten")
        rot.Transform:SetPosition(inst.Transform:GetWorldPosition())
        if gym then
            slot = gym.components.inventory:GetItemSlot(inst)
        end
    end

    inst:Remove()

    if gym and rot then
        gym.components.mightygym:LoadWeight(rot, slot)
    end
end

-- 种子显示名称,根据是否研究过
local function Seed_GetDisplayName(inst)
    local registry_key = inst.plant_def.product

    local plantregistryinfo = inst.plant_def.plantregistryinfo
    return (ThePlantRegistry:KnowsSeed(registry_key, plantregistryinfo) and ThePlantRegistry:KnowsPlantName(registry_key, plantregistryinfo)) and STRINGS.NAMES["KNOWN_" .. string.upper(inst.prefab)]
        or nil
end

local function Oversized_OnSave(inst, data)
    data.from_plant = inst.from_plant or false
    data.harvested_on_day = inst.harvested_on_day
end

local function Oversized_OnPreLoad(inst, data)
    inst.from_plant = (data and data.from_plant) ~= false
    if data ~= nil then
        inst.harvested_on_day = data.harvested_on_day
    end
end

-- 显示形容函数:打蜡的xx
local function displayadjectivefn(inst)
    return STRINGS.UI.HUD.WAXED
end

-- 打蜡函数
local function dowaxfn(inst, doer, waxitem)
    local waxedveggie = SpawnPrefab(inst.prefab .. "_waxed")

    if doer.components.inventory and doer.components.inventory:IsHeavyLifting() and doer.components.inventory:GetEquippedItem(EQUIPSLOTS.BODY) == inst then
        doer.components.inventory:Unequip(EQUIPSLOTS.BODY)
        doer.components.inventory:Equip(waxedveggie)
    else
        waxedveggie.Transform:SetPosition(inst.Transform:GetWorldPosition())
        waxedveggie.AnimState:PlayAnimation("wax_oversized", false)
        waxedveggie.AnimState:PushAnimation("idle_oversized")
    end

    if inst.components.pumpkincarvable ~= nil then
        inst.components.pumpkincarvable:TransferComponent(waxedveggie)
    end

    inst:Remove()

    return true
end

local PlayWaxAnimation

-- 关闭打蜡巨大作物的动画闪烁任务
local function CancelWaxTask(inst)
    if inst._waxtask ~= nil then
        inst._waxtask:Cancel()
        inst._waxtask = nil
    end
end

-- 开启打蜡巨大作物的动画闪烁任务
local function StartWaxTask(inst)
    if not inst.inlimbo and inst._waxtask == nil then
        inst._waxtask = inst:DoTaskInTime(GetRandomMinMax(20, 40), PlayWaxAnimation)
    end
end

PlayWaxAnimation = function(inst)
    inst.AnimState:PlayAnimation("wax_oversized", false)
    inst.AnimState:PushAnimation("idle_oversized")
end

local function MakeVeggie(name, has_seeds)
    -- 产物的动画贴图资源
    local assets =
    {
        Asset("ANIM", "anim/" .. name .. ".zip"),
        Asset("INV_IMAGE", name),
    }
    -- 鱼饵钓竿动画资源(基本没有)
    if SW_VEGGIES[name].lure_data ~= nil then
        table.insert(assets, Asset("ANIM", "anim/" .. SW_VEGGIES[name].lure_data.build .. ".zip"))
    end

    -- 腐烂巨大作物的图标
    table.insert(assets, Asset("INV_IMAGE", name .. "_oversized_rot"))

    -- 如果有种子,添加海钓竿鱼饵在钩子上显示的资源
    if has_seeds then
        table.insert(assets, Asset("ANIM", "anim/oceanfishing_lure_mis.zip"))
    end

    -- 添加产物烤熟的动画贴图资源
    local assets_cooked =
    {
        Asset("ANIM", "anim/" .. name .. ".zip"),
        Asset("INV_IMAGE", name .. "_cooked"),
    }

    -- 其他预制物代码
    local prefabs =
    {
        name .. "_cooked",
        "spoiled_food",
    }

    if has_seeds then
        table.insert(prefabs, name .. "_seeds")
    end

    local dryable = SW_VEGGIES[name].dryable

    -- 如果可以被晾晒
    local assets_dried = {}
    if dryable ~= nil then
        table.insert(prefabs, name .. "_dried")
        table.insert(assets_dried, Asset("ANIM", "anim/" .. dryable.build .. ".zip"))
    end

    local plant_def = PLANT_DEFS[name]
    -- 植株预制物
    local seeds_prefabs = has_seeds and { "farm_plant_" .. name } or nil

    -- 巨大作物相关动画资源
    local assets_oversized = {}
    if has_seeds then
        table.insert(prefabs, name .. "_oversized")
        table.insert(prefabs, name .. "_oversized_waxed")
        table.insert(prefabs, name .. "_oversized_rotten")
        table.insert(prefabs, "splash_green")

        table.insert(assets_oversized, Asset("ANIM", "anim/" .. plant_def.build .. ".zip"))
    end

    -- 种子预制体函数
    local function fn_seeds()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank("sw_farm_plant_seeds")
        inst.AnimState:SetBuild("sw_farm_plant_seeds")
        inst.AnimState:PlayAnimation(name)
        inst.AnimState:SetRayTestOnBB(true)

        inst.pickupsound = "vegetation_firm"

        -- 可烹饪（来自可烹饪组件）已添加到原始状态以进行优化
        inst:AddTag("cookable")
        inst:AddTag("deployedplant")
        inst:AddTag("deployedfarmplant")
        inst:AddTag("oceanfishing_lure")

        inst.overridedeployplacername = "seeds_placer"

        inst.plant_def = plant_def
        inst.displaynamefn = Seed_GetDisplayName

        inst._custom_candeploy_fn = can_plant_seed

        MakeInventoryFloatable(inst)

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("edible")
        inst.components.edible.foodtype = FOODTYPE.SEEDS

        inst:AddComponent("stackable")
        inst.components.stackable.maxsize = TUNING.STACK_SIZE_SMALLITEM

        inst:AddComponent("tradable")
        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")

        inst.components.edible.healthvalue = TUNING.HEALING_TINY / 2
        inst.components.edible.hungervalue = TUNING.CALORIES_TINY

        inst:AddComponent("perishable")
        inst.components.perishable:SetPerishTime(TUNING.PERISH_SUPERSLOW)
        inst.components.perishable:StartPerishing()
        inst.components.perishable.onperishreplacement = "spoiled_food"

        inst:AddComponent("cookable")
        inst.components.cookable.product = "seeds_cooked"

        inst:AddComponent("bait")

        inst:AddComponent("farmplantable")
        inst.components.farmplantable.plant = "farm_plant_" .. name

        -- 已经弃用 (用于给旧档已经建造了的老版农场)
        inst:AddComponent("plantable")
        inst.components.plantable.growtime = TUNING.SEEDS_GROW_TIME
        inst.components.plantable.product = name

        -- 已经弃用 (现在是植物人专属)
        inst:AddComponent("deployable")
        inst.components.deployable:SetDeployMode(DEPLOYMODE.CUSTOM) -- 用函数 inst._custom_candeploy_fn
        inst.components.deployable.restrictedtag = "plantkin"
        inst.components.deployable.ondeploy = OnDeploy

        inst:AddComponent("oceanfishingtackle")
        inst.components.oceanfishingtackle:SetupLure({ build = "oceanfishing_lure_mis", symbol = "hook_seeds", single_use = true, lure_data = TUNING.OCEANFISHING_LURE.SEED })

        MakeSmallBurnable(inst)
        MakeSmallPropagator(inst)

        MakeHauntableLaunchAndPerish(inst)

        return inst
    end

    local function fn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank(name)
        inst.AnimState:SetBuild(name)
        inst.AnimState:PlayAnimation("idle")

        inst.pickupsound = "vegetation_firm"

        inst:AddTag("cookable")

        if dryable ~= nil then
            inst:AddTag("dryable")
        end

        if not SEEDLESS[name] then
            inst:AddTag("weighable_OVERSIZEDVEGGIES")
        end

        local float = SW_VEGGIES[name].float_settings
        if float ~= nil then
            MakeInventoryFloatable(inst, float[1], float[2], float[3])
        else
            MakeInventoryFloatable(inst)
        end

        if SW_VEGGIES[name].lure_data ~= nil then
            inst:AddTag("oceanfishing_lure")
        end

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("edible")
        inst.components.edible.healthvalue = SW_VEGGIES[name].health
        inst.components.edible.hungervalue = SW_VEGGIES[name].hunger
        inst.components.edible.sanityvalue = SW_VEGGIES[name].sanity or 0
        inst.components.edible.foodtype = FOODTYPE.VEGGIE
        inst.components.edible.secondaryfoodtype = SW_VEGGIES[name].secondary_foodtype

        inst:AddComponent("perishable")
        inst.components.perishable:SetPerishTime(SW_VEGGIES[name].perishtime)
        inst.components.perishable:StartPerishing()
        inst.components.perishable.onperishreplacement = "spoiled_food"

        inst:AddComponent("stackable")
        inst.components.stackable.maxsize = TUNING.STACK_SIZE_SMALLITEM

        if dryable ~= nil then
            inst:AddComponent("dryable")
            inst.components.dryable:SetProduct(name .. "_dried")
            inst.components.dryable:SetBuildFile(dryable.build)
            inst.components.dryable:SetDryTime(dryable.time)
        end

        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")

        -- 普通作物都可以称重但都没有具体重量,在农产品称上的重量显示均相同,可以被任意其他农作物替换下来
        if not SEEDLESS[name] then
            inst:AddComponent("weighable")
            inst.components.weighable.type = TROPHYSCALE_TYPES.OVERSIZEDVEGGIES
        end

        MakeSmallBurnable(inst)
        MakeSmallPropagator(inst)

        inst:AddComponent("bait")
        inst:AddComponent("tradable")

        inst:AddComponent("cookable")
        inst.components.cookable.product = name .. "_cooked"

        if SW_VEGGIES[name].lure_data ~= nil then
            inst:AddComponent("oceanfishingtackle")
            inst.components.oceanfishingtackle:SetupLure(SW_VEGGIES[name].lure_data)
        end

        local halloweenmoonmutable_settings = SW_VEGGIES[name].halloweenmoonmutable_settings
        if halloweenmoonmutable_settings ~= nil then
            inst:AddComponent("halloweenmoonmutable")
            inst.components.halloweenmoonmutable:SetPrefabMutated(halloweenmoonmutable_settings.prefab)
            inst.components.halloweenmoonmutable:SetOnMutateFn(halloweenmoonmutable_settings.onmutatefn)
        end

        if SW_VEGGIES[name].issnowmandecor then
            inst:AddComponent("snowmandecor")
        end

        MakeHauntableLaunchAndPerish(inst)

        return inst
    end

    local function fn_cooked()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank(name)
        inst.AnimState:SetBuild(name)
        inst.AnimState:PlayAnimation("cooked")

        local float = SW_VEGGIES[name].cooked_float_settings
        if float ~= nil then
            MakeInventoryFloatable(inst, float[1], float[2], float[3])
        else
            MakeInventoryFloatable(inst)
        end

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("perishable")
        inst.components.perishable:SetPerishTime(SW_VEGGIES[name].cooked_perishtime)
        inst.components.perishable:StartPerishing()
        inst.components.perishable.onperishreplacement = "spoiled_food"

        inst:AddComponent("edible")
        inst.components.edible.healthvalue = SW_VEGGIES[name].cooked_health
        inst.components.edible.hungervalue = SW_VEGGIES[name].cooked_hunger
        inst.components.edible.sanityvalue = SW_VEGGIES[name].cooked_sanity or 0
        inst.components.edible.foodtype = FOODTYPE.VEGGIE
        inst.components.edible.secondaryfoodtype = SW_VEGGIES[name].secondary_foodtype

        inst:AddComponent("stackable")
        inst.components.stackable.maxsize = TUNING.STACK_SIZE_SMALLITEM

        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")

        MakeSmallBurnable(inst)
        MakeSmallPropagator(inst)

        inst:AddComponent("bait")
        inst:AddComponent("tradable")

        MakeHauntableLaunchAndPerish(inst)

        return inst
    end

    local function fn_dried()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank(dryable.build)
        inst.AnimState:SetBuild(dryable.build)
        inst.AnimState:PlayAnimation("dried_" .. name)

        MakeInventoryFloatable(inst)

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("perishable")
        inst.components.perishable:SetPerishTime(dryable.perish)
        inst.components.perishable:StartPerishing()
        inst.components.perishable.onperishreplacement = "spoiled_food"

        inst:AddComponent("edible")
        inst.components.edible.healthvalue = dryable.health or 0
        inst.components.edible.hungervalue = dryable.hunger or 0
        inst.components.edible.sanityvalue = dryable.sanity or 0
        inst.components.edible.foodtype = FOODTYPE.VEGGIE

        inst:AddComponent("stackable")
        inst.components.stackable.maxsize = TUNING.STACK_SIZE_SMALLITEM

        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")

        MakeSmallBurnable(inst)
        MakeSmallPropagator(inst)

        inst:AddComponent("bait")
        inst:AddComponent("tradable")

        MakeHauntableLaunchAndPerish(inst)

        return inst
    end

    local function fn_oversized()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        inst.AnimState:SetBank(plant_def.bank)
        inst.AnimState:SetBuild(plant_def.build)
        inst.AnimState:PlayAnimation("idle_oversized")

        inst:AddTag("heavy")
        inst:AddTag("waxable")
        inst:AddTag("oversized_veggie")
        inst:AddTag("show_spoilage")
        inst.gymweight = 4

        MakeHeavyObstaclePhysics(inst, OVERSIZED_PHYSICS_RADIUS)
        inst:SetPhysicsRadiusOverride(OVERSIZED_PHYSICS_RADIUS)

        inst._base_name = name

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst.harvested_on_day = inst.harvested_on_day or (TheWorld.state.cycles + 1) -- 收获日期,游戏里的世界时间

        inst:AddComponent("heavyobstaclephysics")
        inst.components.heavyobstaclephysics:SetRadius(OVERSIZED_PHYSICS_RADIUS)

        inst:AddComponent("perishable")
        inst.components.perishable:SetPerishTime(SW_VEGGIES[name].perishtime * OVERSIZED_PERISHTIME_MULT)
        inst.components.perishable:StartPerishing()
        inst.components.perishable.onperishreplacement = nil
        inst.components.perishable:SetOnPerishFn(oversized_onperish)

        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")
        inst.components.inventoryitem.cangoincontainer = false
        inst.components.inventoryitem:SetSinks(true)

        inst:AddComponent("equippable")
        inst.components.equippable.equipslot = EQUIPSLOTS.BODY
        inst.components.equippable:SetOnEquip(oversized_onequip)
        inst.components.equippable:SetOnUnequip(oversized_onunequip)
        inst.components.equippable.walkspeedmult = TUNING.HEAVY_SPEED_MULT

        inst:AddComponent("workable")
        inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
        inst.components.workable:SetOnFinishCallback(oversized_onfinishwork)
        inst.components.workable:SetWorkLeft(OVERSIZED_MAXWORK)

        inst:AddComponent("waxable")
        inst.components.waxable:SetWaxfn(dowaxfn)

        inst:AddComponent("submersible")
        inst:AddComponent("symbolswapdata")
        inst.components.symbolswapdata:SetData(plant_def.build, "swap_body")

        local weight_data = plant_def.weight_data
        inst:AddComponent("weighable")
        inst.components.weighable.type = TROPHYSCALE_TYPES.OVERSIZEDVEGGIES
        inst.components.weighable:Initialize(weight_data[1], weight_data[2])
        local coefficient = oversized_calcweightcoefficient(name)
        inst.components.weighable:SetWeight(Lerp(weight_data[1], weight_data[2], coefficient))

        inst:AddComponent("lootdropper")
        inst.components.lootdropper:SetLoot(oversized_makeloots(inst, name))

        MakeMediumBurnable(inst)
        inst.components.burnable:SetOnBurntFn(oversized_onburnt)
        MakeMediumPropagator(inst)

        MakeHauntableWork(inst)

        inst.from_plant = false

        inst.OnSave = Oversized_OnSave
        inst.OnPreLoad = Oversized_OnPreLoad

        return inst
    end

    local function fn_oversized_waxed()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        inst.AnimState:SetBank(plant_def.bank)
        inst.AnimState:SetBuild(plant_def.build)
        inst.AnimState:PlayAnimation("idle_oversized")

        inst:AddTag("heavy")
        inst:AddTag("oversized_veggie")

        inst.gymweight = 4

        inst.displayadjectivefn = displayadjectivefn
        inst:SetPrefabNameOverride(name .. "_oversized")

        MakeHeavyObstaclePhysics(inst, OVERSIZED_PHYSICS_RADIUS)
        inst:SetPhysicsRadiusOverride(OVERSIZED_PHYSICS_RADIUS)

        inst._base_name = name

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("heavyobstaclephysics")
        inst.components.heavyobstaclephysics:SetRadius(OVERSIZED_PHYSICS_RADIUS)

        inst:AddComponent("inspectable")
        inst:AddComponent("inventoryitem")
        inst.components.inventoryitem.cangoincontainer = false
        inst.components.inventoryitem:SetSinks(true)

        inst:AddComponent("equippable")
        inst.components.equippable.equipslot = EQUIPSLOTS.BODY
        inst.components.equippable:SetOnEquip(oversized_onequip)
        inst.components.equippable:SetOnUnequip(oversized_onunequip)
        inst.components.equippable.walkspeedmult = TUNING.HEAVY_SPEED_MULT

        inst:AddComponent("workable")
        inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
        inst.components.workable:SetOnFinishCallback(oversized_onfinishwork)
        inst.components.workable:SetWorkLeft(OVERSIZED_MAXWORK)

        inst:AddComponent("submersible")
        inst:AddComponent("symbolswapdata")
        inst.components.symbolswapdata:SetData(plant_def.build, "swap_body")

        inst:AddComponent("lootdropper")
        inst.components.lootdropper:SetLoot({ "spoiled_food" })

        MakeMediumBurnable(inst)
        inst.components.burnable:SetOnBurntFn(oversized_onburnt)
        MakeMediumPropagator(inst)

        MakeHauntableWork(inst)

        inst:ListenForEvent("onputininventory", CancelWaxTask)
        inst:ListenForEvent("ondropped", StartWaxTask)

        inst.OnEntitySleep = CancelWaxTask
        inst.OnEntityWake = StartWaxTask

        StartWaxTask(inst)

        return inst
    end

    local function fn_oversized_rotten()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddNetwork()

        inst.AnimState:SetBank(plant_def.bank)
        inst.AnimState:SetBuild(plant_def.build)
        inst.AnimState:PlayAnimation("idle_rot_oversized")

        inst:AddTag("heavy")
        inst:AddTag("farm_plant_killjoy")
        inst:AddTag("pickable_harvest_str")
        inst:AddTag("pickable")
        inst:AddTag("oversized_veggie")
        inst.gymweight = 3

        MakeHeavyObstaclePhysics(inst, OVERSIZED_PHYSICS_RADIUS)
        inst:SetPhysicsRadiusOverride(OVERSIZED_PHYSICS_RADIUS)

        inst._base_name = name

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst:AddComponent("heavyobstaclephysics")
        inst.components.heavyobstaclephysics:SetRadius(OVERSIZED_PHYSICS_RADIUS)

        inst:AddComponent("inspectable")
        inst.components.inspectable.nameoverride = "VEGGIE_OVERSIZED_ROTTEN"

        inst:AddComponent("workable")
        inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
        inst.components.workable:SetOnFinishCallback(oversized_onfinishwork)
        inst.components.workable:SetWorkLeft(OVERSIZED_MAXWORK)

        inst:AddComponent("pickable")
        inst.components.pickable.remove_when_picked = true
        inst.components.pickable:SetUp(nil)
        inst.components.pickable.use_lootdropper_for_product = true
        inst.components.pickable.picksound = "dontstarve/wilson/harvest_berries"

        inst:AddComponent("inventoryitem")
        inst.components.inventoryitem.cangoincontainer = false
        inst.components.inventoryitem:SetSinks(true)

        inst:AddComponent("equippable")
        inst.components.equippable.equipslot = EQUIPSLOTS.BODY
        inst.components.equippable:SetOnEquip(oversized_onequip)
        inst.components.equippable:SetOnUnequip(oversized_onunequip)
        inst.components.equippable.walkspeedmult = TUNING.HEAVY_SPEED_MULT

        inst:AddComponent("submersible")
        inst:AddComponent("symbolswapdata")
        inst.components.symbolswapdata:SetData(plant_def.build, "swap_body_rotten")

        inst:AddComponent("lootdropper")
        inst.components.lootdropper:SetLoot(plant_def.loot_oversized_rot)

        MakeMediumBurnable(inst)
        inst.components.burnable:SetOnBurntFn(oversized_onburnt)
        MakeMediumPropagator(inst)

        MakeHauntableWork(inst)

        return inst
    end

    local exported_prefabs = {}

    if has_seeds then
        table.insert(exported_prefabs, Prefab(name .. "_seeds", fn_seeds, assets_seeds, seeds_prefabs))
        table.insert(exported_prefabs, Prefab(name .. "_oversized", fn_oversized, assets_oversized))
        table.insert(exported_prefabs, Prefab(name .. "_oversized_waxed", fn_oversized_waxed, assets_oversized))
        table.insert(exported_prefabs, Prefab(name .. "_oversized_rotten", fn_oversized_rotten, assets_oversized))
    end
    if dryable ~= nil then
        table.insert(exported_prefabs, Prefab(name .. "_dried", fn_dried, assets_dried))
    end

    table.insert(exported_prefabs, Prefab(name, fn, assets, prefabs))
    table.insert(exported_prefabs, Prefab(name .. "_cooked", fn_cooked, assets_cooked))

    return exported_prefabs
end

local prefs = {}
for veggiename, veggiedata in pairs(SW_VEGGIES) do
    local veggies = MakeVeggie(veggiename, not SEEDLESS[veggiename])
    for _, v in ipairs(veggies) do
        table.insert(prefs, v)
    end
end

return unpack(prefs)
